Univariate Exploration

Load the Dataset

# Load dataset

# Load necessary libraries
library(readr)
Warning: package ‘readr’ was built under R version 4.4.3
library(dplyr)  # %>% 
Warning: package ‘dplyr’ was built under R version 4.4.3
Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
# Read the CSV file from Google Drive
url <- "https://drive.google.com/uc?export=download&id=12CriTyCML_9rGAySnmcitxXbij7N11T0"
my_data <- read_csv(url)
Rows: 920 Columns: 16── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (6): sex, dataset, cp, restecg, slope, thal
dbl (8): id, age, trestbps, chol, thalch, oldpeak, ca, num
lgl (2): fbs, exang
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# View the first few rows
head(my_data)
NA
NA
NA
#Removing Unnecessary Columns

my_data <- my_data %>%
  select(-id, -dataset)
print(my_data)
NA
#Checking for Missing Data

my_data[!complete.cases(my_data),]
#Handling Missing Values

my_data[is.na(my_data$trestbps),]


# Calculate the median
trestbps_medv <- median(my_data$trestbps, na.rm = TRUE)
print(trestbps_medv)
[1] 130
# Replace NA values with the median
my_data$trestbps[is.na(my_data$trestbps)] <- trestbps_medv

# Print updated data
print(my_data)


my_data[is.na(my_data$trestbps),]
NA
# Handling Missing Values

my_data[is.na(my_data$chol),]

chol_meanv<- mean(my_data$chol, na.rm=TRUE)
print(chol_meanv)
[1] 199.1303
my_data$chol[is.na(my_data$chol)] <- chol_meanv

print(my_data)
my_data[is.na(my_data$chol),]
# Handling Missing Values

my_data[is.na(my_data$thalch),]

thalch_meanv<- mean(my_data$thalch, na.rm=TRUE)
print(thalch_meanv)
[1] 137.5457
my_data$thalch[is.na(my_data$thalch)] <- thalch_meanv

print(my_data)
my_data[is.na(my_data$thalch),]
# Handling Missing Values

my_data[is.na(my_data$ca),]

# Find the most frequent value (mode) for 'ca'
mode_ca <- as.numeric(names(sort(table(my_data$ca), decreasing = TRUE))[1])
print(mode_ca)
[1] 0
my_data$ca[is.na(my_data$ca)] <- mode_ca


print(my_data)


my_data[is.na(my_data$ca),]
NA
# Handling Missing Values

my_data[is.na(my_data$thal),]

# Find the most frequent value (mode) for 'thal'
mode_thal <- as.character(names(sort(table(my_data$thal), decreasing = TRUE))[1])
print(mode_thal)
[1] "normal"
my_data$thal[is.na(my_data$thal)] <- mode_thal


print(my_data)


my_data[is.na(my_data$thal),]
NA
# Handling Missing Values

my_data[is.na(my_data$slope),]

# Find the most frequent value (mode) for 'slope'
mode_slope <- names(sort(table(my_data$slope), decreasing = TRUE))[1]
print(mode_slope)
[1] "flat"
my_data$slope[is.na(my_data$slope)] <- mode_slope


print(my_data)

my_data[is.na(my_data$slope),]
NA
#Checking for Remaining Missing Data

my_data[!complete.cases(my_data),]
#Remove records with missing values
my_data<-my_data[complete.cases(my_data),]
print(my_data)
my_data[!complete.cases(my_data),]

Summary Statistics

# Summary of numeric variables
summary(my_data)
      age            sex                 cp               trestbps          chol          fbs         
 Min.   :28.00   Length:770         Length:770         Min.   :  0.0   Min.   :  0.0   Mode :logical  
 1st Qu.:46.00   Class :character   Class :character   1st Qu.:120.0   1st Qu.:198.0   FALSE:656      
 Median :54.00   Mode  :character   Mode  :character   Median :130.0   Median :228.5   TRUE :114      
 Mean   :53.04                                         Mean   :132.8   Mean   :218.8                  
 3rd Qu.:59.75                                         3rd Qu.:140.0   3rd Qu.:269.0                  
 Max.   :77.00                                         Max.   :200.0   Max.   :603.0                  
   restecg              thalch        exang            oldpeak           slope                 ca       
 Length:770         Min.   : 60.0   Mode :logical   Min.   :-1.0000   Length:770         Min.   :0.000  
 Class :character   1st Qu.:120.0   FALSE:466       1st Qu.: 0.0000   Class :character   1st Qu.:0.000  
 Mode  :character   Median :140.0   TRUE :304       Median : 0.5000   Mode  :character   Median :0.000  
                    Mean   :138.6                   Mean   : 0.8874                      Mean   :0.261  
                    3rd Qu.:159.0                   3rd Qu.: 1.5000                      3rd Qu.:0.000  
                    Max.   :202.0                   Max.   : 6.2000                      Max.   :3.000  
     thal                num        
 Length:770         Min.   :0.0000  
 Class :character   1st Qu.:0.0000  
 Mode  :character   Median :1.0000  
                    Mean   :0.9195  
                    3rd Qu.:1.0000  
                    Max.   :4.0000  

Univariate Plots


hist(my_data$age, 
     main="Histogram of Age", 
     col="lightblue", 
     xlab="Age", 
     border="black")


hist(my_data$trestbps, 
     main="Histogram of trestbps ", 
     col="green", 
     xlab="trestbps", 
     border="black")

Univariate Plots


hist(my_data$chol, 
     main="Histogram of chol", 
     col="yellow", 
     xlab="chol", 
     border="black")

Univariate Plots


hist(my_data$thalch, 
     main="Histogram of thalch ", 
     col="blue", 
     xlab="thalch", 
     border="black")


hist(my_data$oldpeak, 
     main="Histogram of oldpeak", 
     col="lightblue", 
     xlab="oldpeak", 
     border="black")


hist(my_data$ca, 
     main="Histogram of Chest pain", 
     col="lightblue", 
     xlab="Ca", 
     border="black")


hist(my_data$num, 
     main="Histogram of num", 
     col="lightblue", 
     xlab="num", 
     border="black")

# Convert 'restecg' to numeric
my_data$restecg <- as.numeric(factor(my_data$restecg, levels = c("normal", "lv hypertrophy", "st-t abnormality")))

# Convert 'fbs' (fasting blood sugar) to numeric
my_data$fbs <- ifelse(my_data$fbs == TRUE, 1, 0)

# Convert 'slope' to numeric
my_data$slope <- as.numeric(factor(my_data$slope, levels = c("upsloping", "flat", "downsloping")))



my_data$num <- as.factor(my_data$num)

# Update the levels of 'num' to represent meaningful categories
levels(my_data$num) <- c("No Heart Disease", "Heart Disease -Type1", "Heart Disease -Type2", "Heart Disease -Type3","Heart Disease -Type4")


str(my_data)
tibble [770 × 14] (S3: tbl_df/tbl/data.frame)
 $ age     : num [1:770] 63 67 67 37 41 56 62 57 63 53 ...
 $ sex     : chr [1:770] "Male" "Male" "Male" "Male" ...
 $ cp      : chr [1:770] "typical angina" "asymptomatic" "asymptomatic" "non-anginal" ...
 $ trestbps: num [1:770] 145 160 120 130 130 120 140 120 130 140 ...
 $ chol    : num [1:770] 233 286 229 250 204 236 268 354 254 203 ...
 $ fbs     : num [1:770] 1 0 0 0 0 0 0 0 0 1 ...
 $ restecg : num [1:770] 2 2 2 1 2 1 2 1 2 2 ...
 $ thalch  : num [1:770] 150 108 129 187 172 178 160 163 147 155 ...
 $ exang   : logi [1:770] FALSE TRUE TRUE FALSE FALSE FALSE ...
 $ oldpeak : num [1:770] 2.3 1.5 2.6 3.5 1.4 0.8 3.6 0.6 1.4 3.1 ...
 $ slope   : num [1:770] 3 2 2 3 1 1 3 1 2 3 ...
 $ ca      : num [1:770] 0 3 2 0 0 0 2 0 1 0 ...
 $ thal    : chr [1:770] "fixed defect" "normal" "reversable defect" "normal" ...
 $ num     : Factor w/ 5 levels "No Heart Disease",..: 1 3 2 1 1 1 4 1 3 2 ...
# Print the first few rows to verify the new labels
head(my_data)
NA

hist(my_data$restecg, 
     main="Histogram of restecg", 
     col="lightblue", 
     xlab="restecg", 
     border="black")


hist(my_data$fbs, 
     main="Histogram of fbs", 
     col="lightblue", 
     xlab="fbs", 
     border="black")


hist(my_data$slope, 
     main="Histogram of slope", 
     col="lightblue", 
     xlab="slope", 
     border="black")


# Density Plot (Smooth Distribution)
plot(density(my_data$age), 
     main="Density Plot of Age", 
     col="blue", 
     lwd=2)


# Density Plot (Smooth Distribution)
plot(density(my_data$trestbps), 
     main="Density Plot of trestbps", 
     col="blue", 
     lwd=2)


# Density Plot (Smooth Distribution)
plot(density(my_data$chol), 
     main="Density Plot of chol ", 
     col="blue", 
     lwd=2)


# Density Plot (Smooth Distribution)
plot(density(my_data$thalch), 
     main="Density Plot of thalch", 
     col="blue", 
     lwd=2)


# Density Plot (Smooth Distribution)
plot(density(my_data$oldpeak), 
     main="Density Plot of oldpeak", 
     col="blue", 
     lwd=2)


# Density Plot (Smooth Distribution)
plot(density(my_data$slope), 
     main="Density Plot of slope", 
     col="blue", 
     lwd=2)


# Density Plot (Smooth Distribution)
plot(density(my_data$restecg), 
     main="Density Plot of restecg", 
     col="blue", 
     lwd=2)


# Boxplot 
boxplot(my_data$age, 
        main="Box Plot of Age", 
        col="orange")


# Boxplot 
boxplot(my_data$trestbps, 
        main="Box Plot of trestbps ", 
        col="orange")


# Boxplot 
boxplot(my_data$chol, 
        main="Box Plot of chol", 
        col="orange")


# Boxplot 
boxplot(my_data$restecg, 
        main="Box Plot of restecg ", 
        col="orange")


# Boxplot 
boxplot(my_data$thalch, 
        main="Box Plot of thalch", 
        col="orange")


# Boxplot 
boxplot(my_data$oldpeak, 
        main="Box Plot of oldpeak", 
        col="orange")

Exploring Categorical Data


# Count of each num of affected people 
table(my_data$num)

    No Heart Disease Heart Disease -Type1 Heart Disease -Type2 Heart Disease -Type3 Heart Disease -Type4 
                 375                  210                   81                   80                   24 
# Bar plot for count
barplot(table(my_data$num), 
        main="Count of num", 
        col=c("red", "green", "blue"))

# Bar plot for  count
barplot(table(my_data$sex), 
        main="Count of sex", 
        col=c("red", "green", "blue"))

# Bar plot for  count
barplot(table(my_data$cp), 
        main="Count of cp", 
        col=c("red", "green", "blue"))

# Bar plot for  count
barplot(table(my_data$trestbps), 
        main="Count of trestbps", 
        col=c("red", "green", "blue"))

# Bar plot for count
barplot(table(my_data$slope), 
        main="Count of slope", 
        col=c("red", "green", "blue"))

# Bar plot for Species count
barplot(table(my_data$thal), 
        main="Count of thal", 
        col=c("red", "green", "blue"))

Multivariate Exploration

Scatter Plot (Numeric vs. Numeric)

# Scatter plot: age vs trestbps
plot(my_data$age, my_data$trestbps, 
     main="Scatter Plot of Age vs trestbps",
     xlab="age", ylab="trestbps",
     col=my_data$num, pch=20)
legend("bottomright", legend=levels(my_data$num), col=1:4, pch=19)

Multivariate Exploration

Scatter Plot (Numeric vs. Numeric)

# Scatter plot: thalch vs chol
plot(my_data$thalch, my_data$chol, 
     main="Scatter Plot of thalch vs chol",
     xlab="thalch", ylab="chol",
     col=my_data$num, pch=20)
legend("topleft", legend=levels(my_data$num), col=1:4, pch=20)

# Scatter plot: thalch vs trestbps
plot(my_data$thalch, my_data$trestbps, 
     main="Scatter Plot of thalch vs trestbps",
     xlab="thalch", ylab="trestbps",
     col=my_data$num, pch=20)
legend("bottomleft", legend=levels(my_data$num), col=1:4, pch=20)

Correlation (Numeric vs. Numeric)

# Correlation between age and trestbs
cor(my_data$age, my_data$trestbps)
[1] 0.2409653
# Correlation between age and trestbs
cor(my_data$age, my_data$chol)
[1] -0.06950973
# Correlation between chol and trestbs
cor(my_data$chol, my_data$trestbps)
[1] 0.0599012
# Correlation between thalch and trestbs
cor(my_data$thalch, my_data$trestbps)
[1] -0.109774
# Correlation between age and thalch
cor(my_data$age, my_data$thalch)
[1] -0.3739944
# Correlation between age and slope
cor(my_data$age, my_data$slope)
[1] 0.06146084

Correlation matrix


# Select only numeric columns
numeric_cols <- sapply(my_data, is.numeric)
correlation_matrix <- cor(my_data[, numeric_cols], use = "complete.obs")

# Print the correlation matrix
print(correlation_matrix)
                 age    trestbps        chol         fbs     restecg      thalch     oldpeak       slope          ca
age       1.00000000  0.24096532 -0.06950973  0.22647439  0.20749775 -0.37399438  0.26091537  0.06146084  0.24956877
trestbps  0.24096532  1.00000000  0.05990120  0.15692273  0.10034657 -0.10977400  0.17080414  0.08097304  0.02804014
chol     -0.06950973  0.05990120  1.00000000  0.02907186 -0.06549605  0.19940868  0.05526483 -0.05522410  0.15579945
fbs       0.22647439  0.15692273  0.02907186  1.00000000  0.12153080 -0.05949849  0.05546392  0.06742084  0.07785460
restecg   0.20749775  0.10034657 -0.06549605  0.12153080  1.00000000 -0.09568953  0.10888213  0.05004836  0.03359935
thalch   -0.37399438 -0.10977400  0.19940868 -0.05949849 -0.09568953  1.00000000 -0.18268766 -0.31214858  0.03739947
oldpeak   0.26091537  0.17080414  0.05526483  0.05546392  0.10888213 -0.18268766  1.00000000  0.31268535  0.23154038
slope     0.06146084  0.08097304 -0.05522410  0.06742084  0.05004836 -0.31214858  0.31268535  1.00000000 -0.10682672
ca        0.24956877  0.02804014  0.15579945  0.07785460  0.03359935  0.03739947  0.23154038 -0.10682672  1.00000000
library(corrplot)
Warning: package ‘corrplot’ was built under R version 4.4.3corrplot 0.95 loaded
# Calculate correlation matrix
numeric_cols <- sapply(my_data, is.numeric)
correlation_matrix <- cor(my_data[, numeric_cols], use = "complete.obs")

# Create heatmap with correlation values
corrplot(correlation_matrix, 
         method = "color",
         type = "upper",
         order = "hclust",
         tl.col = "black",
         tl.srt = 45,
         addCoef.col = "black",
         number.cex = 0.7,
         diag = FALSE)

library(plotly)
Warning: package ‘plotly’ was built under R version 4.4.3Loading required package: ggplot2
Warning: package ‘ggplot2’ was built under R version 4.4.3Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
plot_ly(
  x = colnames(correlation_matrix),
  y = colnames(correlation_matrix),
  z = correlation_matrix,
  type = "heatmap",
  colorscale = "Viridis",
  hoverinfo = "x+y+z"
) %>% 
  layout(
    title = "Interactive Correlation Matrix",
    xaxis = list(tickangle = 45),
    margin = list(l = 100, r = 100, b = 150)
  )

Box Plot (Numeric vs. Categorical)

# Boxplot: Cholesterol by Heart Disease Status
boxplot(chol ~ num, data = my_data,
        main = "Boxplot of Cholesterol by Heart Disease Status",
        xlab = "Heart Disease",
        ylab = "Cholesterol",
        col = c("skyblue", "tomato"))

Box Plot (Numeric vs. Categorical)

# Boxplot: Thalch by Sex
boxplot(thalch ~ sex, data = my_data,
        main = "Boxplot of Max Heart Rate by Sex",
        xlab = "Sex",
        ylab = "Max Heart Rate (thalch)",
        col = c("lightpink", "lightblue"))

NA
NA

Box Plot (Numeric vs. Categorical)

# Boxplot: Oldpeak by Chest Pain Type
boxplot(oldpeak ~ cp, data = my_data,
        main = "Boxplot of ST Depression (Oldpeak) by Chest Pain Type",
        xlab = "Chest Pain Type",
        ylab = "Oldpeak",
        col = rainbow(length(unique(my_data$cp))))

Box Plot (Numeric vs. Categorical)

# Boxplot: Trestbps by Slope
boxplot(trestbps ~ slope, data = my_data,
        main = "Boxplot of Resting BP by Slope of Peak Exercise ST",
        xlab = "Slope Type",
        ylab = "Resting Blood Pressure",
        col = c("orange", "cyan", "lightgreen"))

library(ggplot2)
ggplot(my_data, aes(x = as.factor(num), y = chol, fill = as.factor(num))) +
  geom_boxplot() +
  ggtitle("Cholesterol Levels by Heart Disease Class") +
  xlab("Heart Disease Class (num)") +
  ylab("Serum Cholesterol (mg/dl)") +
  theme_minimal()

NA
NA
NA

install.packages(“ggplot2”) # Pair Plot (Multivariate Visualization)


pairs(my_data[, c("age", "trestbps", "chol", "thalch", "oldpeak")],
      col = my_data$num,
      pch = 19,
      main = "Pair Plot of Selected Features Colored by Heart Disease")

Using ggplot2 for Enhanced Visualization


library(ggplot2)

my_data$num <- as.factor(my_data$num)


ggplot(my_data, aes(x = age, y = thalch, color = num)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  ggtitle("Age vs Max Heart Rate (thalch) by Heart Disease Class") +
  xlab("Age") +
  ylab("Max Heart Rate Achieved (thalch)")

NA
NA

Using ggplot2 for Enhanced Visualization




ggplot(my_data, aes(x = trestbps, y = chol, color = num)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  ggtitle("Resting Blood Pressure vs Cholesterol by Heart Disease Class") +
  xlab("Resting Blood Pressure (trestbps)") +
  ylab("Serum Cholesterol (chol)")

NA
NA

Using ggplot2 for Enhanced Visualization


# Scatter plot with regression line for thalch vs chol
ggplot(my_data, aes(x = thalch, y = chol, color = num)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  ggtitle("Maximum Heart Rate Achieved vs Cholesterol by Heart Disease Class") +
  xlab("Maximum Heart Rate Achieved (thalch)") +
  ylab("Serum Cholesterol (chol)") +
  theme_minimal()

NA
NA
NA
NA

Using ggplot2 for Enhanced Visualization

# Load necessary libraries
library(caret)
Warning: package ‘caret’ was built under R version 4.4.3Loading required package: lattice
# Select numeric columns for scaling
numeric_cols <- c("age", "trestbps", "chol", "thalch", "oldpeak")

# Apply standardization (z-score normalization)
preProcValues <- preProcess(my_data[, numeric_cols], method = c("center", "scale"))
my_data_scaled <- predict(preProcValues, my_data[, numeric_cols])

# Replace original columns with scaled versions
my_data[, numeric_cols] <- my_data_scaled

# Verify the transformation
summary(my_data[, numeric_cols])
      age             trestbps            chol             thalch            oldpeak       
 Min.   :-2.6527   Min.   :-7.1575   Min.   :-2.3638   Min.   :-3.04530   Min.   :-1.7406  
 1st Qu.:-0.7459   1st Qu.:-0.6883   1st Qu.:-0.2244   1st Qu.:-0.72072   1st Qu.:-0.8184  
 Median : 0.1015   Median :-0.1492   Median : 0.1052   Median : 0.05414   Median :-0.3573  
 Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.0000  
 3rd Qu.: 0.7106   3rd Qu.: 0.3899   3rd Qu.: 0.5428   3rd Qu.: 0.79026   3rd Qu.: 0.5649  
 Max.   : 2.5380   Max.   : 3.6245   Max.   : 4.1519   Max.   : 2.45621   Max.   : 4.8994  
# Binary encoding for sex (Male=1, Female=0)
my_data$sex <- ifelse(my_data$sex == "Male", 1, 0)

# Ensure fbs is integer (fasting blood sugar > 120 mg/dl)
my_data$fbs <- as.integer(my_data$fbs)

# Convert target variable to factor
my_data$num <- as.factor(my_data$num)

# Identify numeric columns for scaling (excluding already encoded categoricals and target)
numeric_cols <- c("age", "trestbps", "chol", "thalch", "oldpeak", "ca")

# Apply standardization (z-score normalization)
library(caret)
preProcValues <- preProcess(my_data[, numeric_cols], method = c("center", "scale"))

# View the structure of your data
str(my_data)
tibble [770 × 14] (S3: tbl_df/tbl/data.frame)
 $ age     : num [1:770] 1.05 1.48 1.48 -1.7 -1.28 ...
 $ sex     : num [1:770] 1 1 1 1 0 1 0 0 1 1 ...
 $ cp      : chr [1:770] "typical angina" "asymptomatic" "asymptomatic" "non-anginal" ...
 $ trestbps: num [1:770] 0.659 1.468 -0.688 -0.149 -0.149 ...
 $ chol    : num [1:770] 0.154 0.727 0.111 0.338 -0.16 ...
 $ fbs     : int [1:770] 1 0 0 0 0 0 0 0 0 1 ...
 $ restecg : num [1:770] 2 2 2 1 2 1 2 1 2 2 ...
 $ thalch  : num [1:770] 0.442 -1.186 -0.372 1.875 1.294 ...
 $ exang   : logi [1:770] FALSE TRUE TRUE FALSE FALSE FALSE ...
 $ oldpeak : num [1:770] 1.303 0.565 1.579 2.409 0.473 ...
 $ slope   : num [1:770] 3 2 2 3 1 1 3 1 2 3 ...
 $ ca      : num [1:770] 0 3 2 0 0 0 2 0 1 0 ...
 $ thal    : chr [1:770] "fixed defect" "normal" "reversable defect" "normal" ...
 $ num     : Factor w/ 5 levels "No Heart Disease",..: 1 3 2 1 1 1 4 1 3 2 ...
# Check the first few rows
head(my_data)
# Function to detect outliers using IQR method
detect_outliers <- function(x) {
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR <- Q3 - Q1
  lower_bound <- Q1 - 1.5 * IQR
  upper_bound <- Q3 + 1.5 * IQR
  x < lower_bound | x > upper_bound
}

# Check for outliers in numeric columns
outliers_list <- lapply(my_data[, numeric_cols], detect_outliers)
outliers_count <- sapply(outliers_list, sum)
print(outliers_count)
     age trestbps     chol   thalch  oldpeak       ca 
       0       25       99        1       15      123 
# Boxplots to visualize outliers
par(mfrow = c(2, 3))
for (col in numeric_cols) {
  boxplot(my_data[[col]], main = paste("Boxplot of", col))
}
par(mfrow = c(1, 1))

# 1. Robust Scaling
library(caret)
robust_scaler <- preProcess(my_data[, numeric_cols],
                          method = c("center", "scale", "YeoJohnson"))
my_data[, numeric_cols] <- predict(robust_scaler, my_data[, numeric_cols])

# 2. Check for zero-IQR columns
zero_iqr_cols <- sapply(my_data[, numeric_cols], function(x) IQR(x, na.rm = TRUE) == 0)
if(any(zero_iqr_cols)) {
  message("Columns with IQR=0: ", paste(names(zero_iqr_cols)[zero_iqr_cols], collapse = ", "))

  valid_cols <- names(zero_iqr_cols)[!zero_iqr_cols]
}
Columns with IQR=0: ca
# 3. Multivariate Outlier Detection (with fallback)
library(MASS)

Attaching package: ‘MASS’

The following object is masked from ‘package:plotly’:

    select

The following object is masked from ‘package:dplyr’:

    select
if(length(valid_cols) > 1) {  # Need at least 2 columns for covariance
  tryCatch({
    mcd <- cov.mcd(my_data[, valid_cols])
    mahalanobis_dist <- mahalanobis(my_data[, valid_cols], 
                                  mcd$center, 
                                  mcd$cov)
    cutoff <- qchisq(0.99, df = length(valid_cols))
    outliers <- mahalanobis_dist > cutoff
    
    # 4. Handle outliers (capping approach)
    my_data[outliers, valid_cols] <- 
      apply(my_data[outliers, valid_cols], 2, 
            function(x) pmin(pmax(x, 
                                 quantile(x, 0.01, na.rm = TRUE)),
                            quantile(x, 0.99, na.rm = TRUE)))
  }, error = function(e) {
    message("MCD failed, using regular Mahalanobis: ", e$message)
    # Fallback to regular Mahalanobis
    mahalanobis_dist <- mahalanobis(my_data[, valid_cols], 
                                  colMeans(my_data[, valid_cols]), 
                                  cov(my_data[, valid_cols]))
    cutoff <- qchisq(0.99, df = length(valid_cols))
    outliers <- mahalanobis_dist > cutoff
  })
} else {
  message("Insufficient valid columns for multivariate outlier detection")
  outliers <- rep(FALSE, nrow(my_data))
}

# 5. Final Verification
summary(my_data[, numeric_cols])
      age                trestbps              chol               thalch              oldpeak         
 Min.   :-2.4825139   Min.   :-2.328135   Min.   :-2.045079   Min.   :-2.6480150   Min.   :-1.986058  
 1st Qu.:-0.7616226   1st Qu.:-0.673934   1st Qu.:-0.333804   1st Qu.:-0.7452664   1st Qu.:-0.956260  
 Median : 0.0639728   Median :-0.107625   Median : 0.001456   Median : 0.0024077   Median :-0.111733  
 Mean   : 0.0005231   Mean   : 0.008183   Mean   :-0.001064   Mean   :-0.0007991   Mean   : 0.001899  
 3rd Qu.: 0.6966808   3rd Qu.: 0.426799   3rd Qu.: 0.498388   3rd Qu.: 0.7790438   3rd Qu.: 0.880230  
 Max.   : 2.7240781   Max.   : 3.268579   Max.   : 5.235235   Max.   : 2.6990588   Max.   : 2.361611  
       ca        
 Min.   :-0.390  
 1st Qu.:-0.390  
 Median :-0.390  
 Mean   : 0.000  
 3rd Qu.:-0.390  
 Max.   : 4.092  
boxplot(my_data[, numeric_cols], main = "Post-Processing Distributions")

LS0tDQp0aXRsZTogIkRhdGEgRXhwbG9yYXRpb24gb24gSGVhcnQgRGlzZWFzZSBEYXRhIg0KYXV0aG9yOiAiR3JvdXAgOSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgd29yZF9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQoNCiMgVW5pdmFyaWF0ZSBFeHBsb3JhdGlvbg0KDQojIExvYWQgdGhlIERhdGFzZXQNCmBgYHtyfQ0KIyBMb2FkIGRhdGFzZXQNCg0KIyBMb2FkIG5lY2Vzc2FyeSBsaWJyYXJpZXMNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGRwbHlyKSAgIyAlPiUgDQoNCiMgUmVhZCB0aGUgQ1NWIGZpbGUgZnJvbSBHb29nbGUgRHJpdmUNCnVybCA8LSAiaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL3VjP2V4cG9ydD1kb3dubG9hZCZpZD0xMkNyaVR5Q01MXzlyR0F5U25tY2l0eFhiaWo3TjExVDAiDQpteV9kYXRhIDwtIHJlYWRfY3N2KHVybCkNCg0KIyBWaWV3IHRoZSBmaXJzdCBmZXcgcm93cw0KaGVhZChteV9kYXRhKQ0KDQoNCg0KYGBgDQpgYGB7cn0NCiNSZW1vdmluZyBVbm5lY2Vzc2FyeSBDb2x1bW5zDQoNCm15X2RhdGEgPC0gbXlfZGF0YSAlPiUNCiAgc2VsZWN0KC1pZCwgLWRhdGFzZXQpDQpwcmludChteV9kYXRhKQ0KDQpgYGANCg0KYGBge3J9DQojQ2hlY2tpbmcgZm9yIE1pc3NpbmcgRGF0YQ0KDQpteV9kYXRhWyFjb21wbGV0ZS5jYXNlcyhteV9kYXRhKSxdDQpgYGANCg0KYGBge3J9DQojSGFuZGxpbmcgTWlzc2luZyBWYWx1ZXMNCg0KbXlfZGF0YVtpcy5uYShteV9kYXRhJHRyZXN0YnBzKSxdDQoNCg0KIyBDYWxjdWxhdGUgdGhlIG1lZGlhbg0KdHJlc3RicHNfbWVkdiA8LSBtZWRpYW4obXlfZGF0YSR0cmVzdGJwcywgbmEucm0gPSBUUlVFKQ0KcHJpbnQodHJlc3RicHNfbWVkdikNCg0KIyBSZXBsYWNlIE5BIHZhbHVlcyB3aXRoIHRoZSBtZWRpYW4NCm15X2RhdGEkdHJlc3RicHNbaXMubmEobXlfZGF0YSR0cmVzdGJwcyldIDwtIHRyZXN0YnBzX21lZHYNCg0KIyBQcmludCB1cGRhdGVkIGRhdGENCnByaW50KG15X2RhdGEpDQoNCg0KbXlfZGF0YVtpcy5uYShteV9kYXRhJHRyZXN0YnBzKSxdDQoNCmBgYA0KYGBge3J9DQojIEhhbmRsaW5nIE1pc3NpbmcgVmFsdWVzDQoNCm15X2RhdGFbaXMubmEobXlfZGF0YSRjaG9sKSxdDQoNCmNob2xfbWVhbnY8LSBtZWFuKG15X2RhdGEkY2hvbCwgbmEucm09VFJVRSkNCnByaW50KGNob2xfbWVhbnYpDQoNCm15X2RhdGEkY2hvbFtpcy5uYShteV9kYXRhJGNob2wpXSA8LSBjaG9sX21lYW52DQoNCnByaW50KG15X2RhdGEpDQpteV9kYXRhW2lzLm5hKG15X2RhdGEkY2hvbCksXQ0KYGBgDQpgYGB7cn0NCiMgSGFuZGxpbmcgTWlzc2luZyBWYWx1ZXMNCg0KbXlfZGF0YVtpcy5uYShteV9kYXRhJHRoYWxjaCksXQ0KDQp0aGFsY2hfbWVhbnY8LSBtZWFuKG15X2RhdGEkdGhhbGNoLCBuYS5ybT1UUlVFKQ0KcHJpbnQodGhhbGNoX21lYW52KQ0KDQpteV9kYXRhJHRoYWxjaFtpcy5uYShteV9kYXRhJHRoYWxjaCldIDwtIHRoYWxjaF9tZWFudg0KDQpwcmludChteV9kYXRhKQ0KbXlfZGF0YVtpcy5uYShteV9kYXRhJHRoYWxjaCksXQ0KYGBgDQpgYGB7cn0NCiMgSGFuZGxpbmcgTWlzc2luZyBWYWx1ZXMNCg0KbXlfZGF0YVtpcy5uYShteV9kYXRhJGNhKSxdDQoNCiMgRmluZCB0aGUgbW9zdCBmcmVxdWVudCB2YWx1ZSAobW9kZSkgZm9yICdjYScNCm1vZGVfY2EgPC0gYXMubnVtZXJpYyhuYW1lcyhzb3J0KHRhYmxlKG15X2RhdGEkY2EpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdKQ0KcHJpbnQobW9kZV9jYSkNCg0KbXlfZGF0YSRjYVtpcy5uYShteV9kYXRhJGNhKV0gPC0gbW9kZV9jYQ0KDQoNCnByaW50KG15X2RhdGEpDQoNCg0KbXlfZGF0YVtpcy5uYShteV9kYXRhJGNhKSxdDQoNCmBgYA0KDQpgYGB7cn0NCiMgSGFuZGxpbmcgTWlzc2luZyBWYWx1ZXMNCg0KbXlfZGF0YVtpcy5uYShteV9kYXRhJHRoYWwpLF0NCg0KIyBGaW5kIHRoZSBtb3N0IGZyZXF1ZW50IHZhbHVlIChtb2RlKSBmb3IgJ3RoYWwnDQptb2RlX3RoYWwgPC0gYXMuY2hhcmFjdGVyKG5hbWVzKHNvcnQodGFibGUobXlfZGF0YSR0aGFsKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSkNCnByaW50KG1vZGVfdGhhbCkNCg0KDQpteV9kYXRhJHRoYWxbaXMubmEobXlfZGF0YSR0aGFsKV0gPC0gbW9kZV90aGFsDQoNCg0KcHJpbnQobXlfZGF0YSkNCg0KDQpteV9kYXRhW2lzLm5hKG15X2RhdGEkdGhhbCksXQ0KDQpgYGANCmBgYHtyfQ0KIyBIYW5kbGluZyBNaXNzaW5nIFZhbHVlcw0KDQpteV9kYXRhW2lzLm5hKG15X2RhdGEkc2xvcGUpLF0NCg0KIyBGaW5kIHRoZSBtb3N0IGZyZXF1ZW50IHZhbHVlIChtb2RlKSBmb3IgJ3Nsb3BlJw0KbW9kZV9zbG9wZSA8LSBuYW1lcyhzb3J0KHRhYmxlKG15X2RhdGEkc2xvcGUpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdDQpwcmludChtb2RlX3Nsb3BlKQ0KDQoNCm15X2RhdGEkc2xvcGVbaXMubmEobXlfZGF0YSRzbG9wZSldIDwtIG1vZGVfc2xvcGUNCg0KDQpwcmludChteV9kYXRhKQ0KDQpteV9kYXRhW2lzLm5hKG15X2RhdGEkc2xvcGUpLF0NCg0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KI0NoZWNraW5nIGZvciBSZW1haW5pbmcgTWlzc2luZyBEYXRhDQoNCm15X2RhdGFbIWNvbXBsZXRlLmNhc2VzKG15X2RhdGEpLF0NCmBgYA0KYGBge3J9DQojUmVtb3ZlIHJlY29yZHMgd2l0aCBtaXNzaW5nIHZhbHVlcw0KbXlfZGF0YTwtbXlfZGF0YVtjb21wbGV0ZS5jYXNlcyhteV9kYXRhKSxdDQpwcmludChteV9kYXRhKQ0KbXlfZGF0YVshY29tcGxldGUuY2FzZXMobXlfZGF0YSksXQ0KYGBgDQoNCiMgU3VtbWFyeSBTdGF0aXN0aWNzDQpgYGB7cn0NCiMgU3VtbWFyeSBvZiBudW1lcmljIHZhcmlhYmxlcw0Kc3VtbWFyeShteV9kYXRhKQ0KDQpgYGANCiMgVW5pdmFyaWF0ZSBQbG90cw0KYGBge3J9DQoNCmhpc3QobXlfZGF0YSRhZ2UsIA0KICAgICBtYWluPSJIaXN0b2dyYW0gb2YgQWdlIiwgDQogICAgIGNvbD0ibGlnaHRibHVlIiwgDQogICAgIHhsYWI9IkFnZSIsIA0KICAgICBib3JkZXI9ImJsYWNrIikNCg0KYGBgDQpgYGB7cn0NCg0KaGlzdChteV9kYXRhJHRyZXN0YnBzLCANCiAgICAgbWFpbj0iSGlzdG9ncmFtIG9mIHRyZXN0YnBzICIsIA0KICAgICBjb2w9ImdyZWVuIiwgDQogICAgIHhsYWI9InRyZXN0YnBzIiwgDQogICAgIGJvcmRlcj0iYmxhY2siKQ0KDQpgYGANCiMgVW5pdmFyaWF0ZSBQbG90cw0KYGBge3J9DQoNCmhpc3QobXlfZGF0YSRjaG9sLCANCiAgICAgbWFpbj0iSGlzdG9ncmFtIG9mIGNob2wiLCANCiAgICAgY29sPSJ5ZWxsb3ciLCANCiAgICAgeGxhYj0iY2hvbCIsIA0KICAgICBib3JkZXI9ImJsYWNrIikNCg0KYGBgDQoNCg0KIyBVbml2YXJpYXRlIFBsb3RzDQpgYGB7cn0NCg0KaGlzdChteV9kYXRhJHRoYWxjaCwgDQogICAgIG1haW49Ikhpc3RvZ3JhbSBvZiB0aGFsY2ggIiwgDQogICAgIGNvbD0iYmx1ZSIsIA0KICAgICB4bGFiPSJ0aGFsY2giLCANCiAgICAgYm9yZGVyPSJibGFjayIpDQoNCmBgYA0KYGBge3J9DQoNCmhpc3QobXlfZGF0YSRvbGRwZWFrLCANCiAgICAgbWFpbj0iSGlzdG9ncmFtIG9mIG9sZHBlYWsiLCANCiAgICAgY29sPSJsaWdodGJsdWUiLCANCiAgICAgeGxhYj0ib2xkcGVhayIsIA0KICAgICBib3JkZXI9ImJsYWNrIikNCg0KYGBgDQpgYGB7cn0NCg0KaGlzdChteV9kYXRhJGNhLCANCiAgICAgbWFpbj0iSGlzdG9ncmFtIG9mIENoZXN0IHBhaW4iLCANCiAgICAgY29sPSJsaWdodGJsdWUiLCANCiAgICAgeGxhYj0iQ2EiLCANCiAgICAgYm9yZGVyPSJibGFjayIpDQoNCmBgYA0KYGBge3J9DQoNCmhpc3QobXlfZGF0YSRudW0sIA0KICAgICBtYWluPSJIaXN0b2dyYW0gb2YgbnVtIiwgDQogICAgIGNvbD0ibGlnaHRibHVlIiwgDQogICAgIHhsYWI9Im51bSIsIA0KICAgICBib3JkZXI9ImJsYWNrIikNCg0KYGBgDQpgYGB7cn0NCiMgQ29udmVydCAncmVzdGVjZycgdG8gbnVtZXJpYw0KbXlfZGF0YSRyZXN0ZWNnIDwtIGFzLm51bWVyaWMoZmFjdG9yKG15X2RhdGEkcmVzdGVjZywgbGV2ZWxzID0gYygibm9ybWFsIiwgImx2IGh5cGVydHJvcGh5IiwgInN0LXQgYWJub3JtYWxpdHkiKSkpDQoNCiMgQ29udmVydCAnZmJzJyAoZmFzdGluZyBibG9vZCBzdWdhcikgdG8gbnVtZXJpYw0KbXlfZGF0YSRmYnMgPC0gaWZlbHNlKG15X2RhdGEkZmJzID09IFRSVUUsIDEsIDApDQoNCiMgQ29udmVydCAnc2xvcGUnIHRvIG51bWVyaWMNCm15X2RhdGEkc2xvcGUgPC0gYXMubnVtZXJpYyhmYWN0b3IobXlfZGF0YSRzbG9wZSwgbGV2ZWxzID0gYygidXBzbG9waW5nIiwgImZsYXQiLCAiZG93bnNsb3BpbmciKSkpDQoNCg0KDQpteV9kYXRhJG51bSA8LSBhcy5mYWN0b3IobXlfZGF0YSRudW0pDQoNCiMgVXBkYXRlIHRoZSBsZXZlbHMgb2YgJ251bScgdG8gcmVwcmVzZW50IG1lYW5pbmdmdWwgY2F0ZWdvcmllcw0KbGV2ZWxzKG15X2RhdGEkbnVtKSA8LSBjKCJObyBIZWFydCBEaXNlYXNlIiwgIkhlYXJ0IERpc2Vhc2UgLVR5cGUxIiwgIkhlYXJ0IERpc2Vhc2UgLVR5cGUyIiwgIkhlYXJ0IERpc2Vhc2UgLVR5cGUzIiwiSGVhcnQgRGlzZWFzZSAtVHlwZTQiKQ0KDQoNCnN0cihteV9kYXRhKQ0KDQojIFByaW50IHRoZSBmaXJzdCBmZXcgcm93cyB0byB2ZXJpZnkgdGhlIG5ldyBsYWJlbHMNCmhlYWQobXlfZGF0YSkNCg0KYGBgDQpgYGB7cn0NCg0KaGlzdChteV9kYXRhJHJlc3RlY2csIA0KICAgICBtYWluPSJIaXN0b2dyYW0gb2YgcmVzdGVjZyIsIA0KICAgICBjb2w9ImxpZ2h0Ymx1ZSIsIA0KICAgICB4bGFiPSJyZXN0ZWNnIiwgDQogICAgIGJvcmRlcj0iYmxhY2siKQ0KDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQpoaXN0KG15X2RhdGEkZmJzLCANCiAgICAgbWFpbj0iSGlzdG9ncmFtIG9mIGZicyIsIA0KICAgICBjb2w9ImxpZ2h0Ymx1ZSIsIA0KICAgICB4bGFiPSJmYnMiLCANCiAgICAgYm9yZGVyPSJibGFjayIpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpoaXN0KG15X2RhdGEkc2xvcGUsIA0KICAgICBtYWluPSJIaXN0b2dyYW0gb2Ygc2xvcGUiLCANCiAgICAgY29sPSJsaWdodGJsdWUiLCANCiAgICAgeGxhYj0ic2xvcGUiLCANCiAgICAgYm9yZGVyPSJibGFjayIpDQoNCmBgYA0KDQoNCg0KDQpgYGB7cn0NCg0KIyBEZW5zaXR5IFBsb3QgKFNtb290aCBEaXN0cmlidXRpb24pDQpwbG90KGRlbnNpdHkobXlfZGF0YSRhZ2UpLCANCiAgICAgbWFpbj0iRGVuc2l0eSBQbG90IG9mIEFnZSIsIA0KICAgICBjb2w9ImJsdWUiLCANCiAgICAgbHdkPTIpDQoNCmBgYA0KYGBge3J9DQoNCiMgRGVuc2l0eSBQbG90IChTbW9vdGggRGlzdHJpYnV0aW9uKQ0KcGxvdChkZW5zaXR5KG15X2RhdGEkdHJlc3RicHMpLCANCiAgICAgbWFpbj0iRGVuc2l0eSBQbG90IG9mIHRyZXN0YnBzIiwgDQogICAgIGNvbD0iYmx1ZSIsIA0KICAgICBsd2Q9MikNCg0KYGBgDQoNCg0KYGBge3J9DQoNCiMgRGVuc2l0eSBQbG90IChTbW9vdGggRGlzdHJpYnV0aW9uKQ0KcGxvdChkZW5zaXR5KG15X2RhdGEkY2hvbCksIA0KICAgICBtYWluPSJEZW5zaXR5IFBsb3Qgb2YgY2hvbCAiLCANCiAgICAgY29sPSJibHVlIiwgDQogICAgIGx3ZD0yKQ0KDQpgYGANCg0KDQpgYGB7cn0NCg0KIyBEZW5zaXR5IFBsb3QgKFNtb290aCBEaXN0cmlidXRpb24pDQpwbG90KGRlbnNpdHkobXlfZGF0YSR0aGFsY2gpLCANCiAgICAgbWFpbj0iRGVuc2l0eSBQbG90IG9mIHRoYWxjaCIsIA0KICAgICBjb2w9ImJsdWUiLCANCiAgICAgbHdkPTIpDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyBEZW5zaXR5IFBsb3QgKFNtb290aCBEaXN0cmlidXRpb24pDQpwbG90KGRlbnNpdHkobXlfZGF0YSRvbGRwZWFrKSwgDQogICAgIG1haW49IkRlbnNpdHkgUGxvdCBvZiBvbGRwZWFrIiwgDQogICAgIGNvbD0iYmx1ZSIsIA0KICAgICBsd2Q9MikNCg0KYGBgDQoNCmBgYHtyfQ0KDQojIERlbnNpdHkgUGxvdCAoU21vb3RoIERpc3RyaWJ1dGlvbikNCnBsb3QoZGVuc2l0eShteV9kYXRhJHNsb3BlKSwgDQogICAgIG1haW49IkRlbnNpdHkgUGxvdCBvZiBzbG9wZSIsIA0KICAgICBjb2w9ImJsdWUiLCANCiAgICAgbHdkPTIpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQojIERlbnNpdHkgUGxvdCAoU21vb3RoIERpc3RyaWJ1dGlvbikNCnBsb3QoZGVuc2l0eShteV9kYXRhJHJlc3RlY2cpLCANCiAgICAgbWFpbj0iRGVuc2l0eSBQbG90IG9mIHJlc3RlY2ciLCANCiAgICAgY29sPSJibHVlIiwgDQogICAgIGx3ZD0yKQ0KDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQojIEJveHBsb3QgDQpib3hwbG90KG15X2RhdGEkYWdlLCANCiAgICAgICAgbWFpbj0iQm94IFBsb3Qgb2YgQWdlIiwgDQogICAgICAgIGNvbD0ib3JhbmdlIikNCg0KYGBgDQoNCg0KYGBge3J9DQoNCiMgQm94cGxvdCANCmJveHBsb3QobXlfZGF0YSR0cmVzdGJwcywgDQogICAgICAgIG1haW49IkJveCBQbG90IG9mIHRyZXN0YnBzICIsIA0KICAgICAgICBjb2w9Im9yYW5nZSIpDQoNCmBgYA0KYGBge3J9DQoNCiMgQm94cGxvdCANCmJveHBsb3QobXlfZGF0YSRjaG9sLCANCiAgICAgICAgbWFpbj0iQm94IFBsb3Qgb2YgY2hvbCIsIA0KICAgICAgICBjb2w9Im9yYW5nZSIpDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyBCb3hwbG90IA0KYm94cGxvdChteV9kYXRhJHJlc3RlY2csIA0KICAgICAgICBtYWluPSJCb3ggUGxvdCBvZiByZXN0ZWNnICIsIA0KICAgICAgICBjb2w9Im9yYW5nZSIpDQoNCmBgYA0KYGBge3J9DQoNCiMgQm94cGxvdCANCmJveHBsb3QobXlfZGF0YSR0aGFsY2gsIA0KICAgICAgICBtYWluPSJCb3ggUGxvdCBvZiB0aGFsY2giLCANCiAgICAgICAgY29sPSJvcmFuZ2UiKQ0KDQpgYGANCg0KYGBge3J9DQoNCiMgQm94cGxvdCANCmJveHBsb3QobXlfZGF0YSRvbGRwZWFrLCANCiAgICAgICAgbWFpbj0iQm94IFBsb3Qgb2Ygb2xkcGVhayIsIA0KICAgICAgICBjb2w9Im9yYW5nZSIpDQoNCmBgYA0KDQoNCg0KIyBFeHBsb3JpbmcgQ2F0ZWdvcmljYWwgRGF0YQ0KYGBge3J9DQoNCiMgQ291bnQgb2YgZWFjaCBudW0gb2YgYWZmZWN0ZWQgcGVvcGxlIA0KdGFibGUobXlfZGF0YSRudW0pDQoNCmBgYA0KDQoNCg0KYGBge3J9DQojIEJhciBwbG90IGZvciBjb3VudA0KYmFycGxvdCh0YWJsZShteV9kYXRhJG51bSksIA0KICAgICAgICBtYWluPSJDb3VudCBvZiBudW0iLCANCiAgICAgICAgY29sPWMoInJlZCIsICJncmVlbiIsICJibHVlIikpDQoNCmBgYA0KYGBge3J9DQojIEJhciBwbG90IGZvciAgY291bnQNCmJhcnBsb3QodGFibGUobXlfZGF0YSRzZXgpLCANCiAgICAgICAgbWFpbj0iQ291bnQgb2Ygc2V4IiwgDQogICAgICAgIGNvbD1jKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIpKQ0KDQpgYGANCmBgYHtyfQ0KIyBCYXIgcGxvdCBmb3IgIGNvdW50DQpiYXJwbG90KHRhYmxlKG15X2RhdGEkY3ApLCANCiAgICAgICAgbWFpbj0iQ291bnQgb2YgY3AiLCANCiAgICAgICAgY29sPWMoInJlZCIsICJncmVlbiIsICJibHVlIikpDQoNCmBgYA0KYGBge3J9DQojIEJhciBwbG90IGZvciAgY291bnQNCmJhcnBsb3QodGFibGUobXlfZGF0YSR0cmVzdGJwcyksIA0KICAgICAgICBtYWluPSJDb3VudCBvZiB0cmVzdGJwcyIsIA0KICAgICAgICBjb2w9YygicmVkIiwgImdyZWVuIiwgImJsdWUiKSkNCg0KYGBgDQpgYGB7cn0NCiMgQmFyIHBsb3QgZm9yIGNvdW50DQpiYXJwbG90KHRhYmxlKG15X2RhdGEkc2xvcGUpLCANCiAgICAgICAgbWFpbj0iQ291bnQgb2Ygc2xvcGUiLCANCiAgICAgICAgY29sPWMoInJlZCIsICJncmVlbiIsICJibHVlIikpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBCYXIgcGxvdCBmb3IgU3BlY2llcyBjb3VudA0KYmFycGxvdCh0YWJsZShteV9kYXRhJHRoYWwpLCANCiAgICAgICAgbWFpbj0iQ291bnQgb2YgdGhhbCIsIA0KICAgICAgICBjb2w9YygicmVkIiwgImdyZWVuIiwgImJsdWUiKSkNCg0KYGBgDQoNCg0KDQoNCg0KIyBNdWx0aXZhcmlhdGUgRXhwbG9yYXRpb24NCiMgU2NhdHRlciBQbG90IChOdW1lcmljIHZzLiBOdW1lcmljKQ0KYGBge3J9DQojIFNjYXR0ZXIgcGxvdDogYWdlIHZzIHRyZXN0YnBzDQpwbG90KG15X2RhdGEkYWdlLCBteV9kYXRhJHRyZXN0YnBzLCANCiAgICAgbWFpbj0iU2NhdHRlciBQbG90IG9mIEFnZSB2cyB0cmVzdGJwcyIsDQogICAgIHhsYWI9ImFnZSIsIHlsYWI9InRyZXN0YnBzIiwNCiAgICAgY29sPW15X2RhdGEkbnVtLCBwY2g9MjApDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgbGVnZW5kPWxldmVscyhteV9kYXRhJG51bSksIGNvbD0xOjQsIHBjaD0xOSkNCg0KYGBgDQojIE11bHRpdmFyaWF0ZSBFeHBsb3JhdGlvbg0KIyBTY2F0dGVyIFBsb3QgKE51bWVyaWMgdnMuIE51bWVyaWMpDQpgYGB7cn0NCiMgU2NhdHRlciBwbG90OiB0aGFsY2ggdnMgY2hvbA0KcGxvdChteV9kYXRhJHRoYWxjaCwgbXlfZGF0YSRjaG9sLCANCiAgICAgbWFpbj0iU2NhdHRlciBQbG90IG9mIHRoYWxjaCB2cyBjaG9sIiwNCiAgICAgeGxhYj0idGhhbGNoIiwgeWxhYj0iY2hvbCIsDQogICAgIGNvbD1teV9kYXRhJG51bSwgcGNoPTIwKQ0KbGVnZW5kKCJ0b3BsZWZ0IiwgbGVnZW5kPWxldmVscyhteV9kYXRhJG51bSksIGNvbD0xOjQsIHBjaD0yMCkNCg0KYGBgDQoNCmBgYHtyfQ0KIyBTY2F0dGVyIHBsb3Q6IHRoYWxjaCB2cyB0cmVzdGJwcw0KcGxvdChteV9kYXRhJHRoYWxjaCwgbXlfZGF0YSR0cmVzdGJwcywgDQogICAgIG1haW49IlNjYXR0ZXIgUGxvdCBvZiB0aGFsY2ggdnMgdHJlc3RicHMiLA0KICAgICB4bGFiPSJ0aGFsY2giLCB5bGFiPSJ0cmVzdGJwcyIsDQogICAgIGNvbD1teV9kYXRhJG51bSwgcGNoPTIwKQ0KbGVnZW5kKCJib3R0b21sZWZ0IiwgbGVnZW5kPWxldmVscyhteV9kYXRhJG51bSksIGNvbD0xOjQsIHBjaD0yMCkNCg0KYGBgDQoNCg0KDQojIENvcnJlbGF0aW9uIChOdW1lcmljIHZzLiBOdW1lcmljKQ0KYGBge3J9DQojIENvcnJlbGF0aW9uIGJldHdlZW4gYWdlIGFuZCB0cmVzdGJzDQpjb3IobXlfZGF0YSRhZ2UsIG15X2RhdGEkdHJlc3RicHMpDQoNCmBgYA0KYGBge3J9DQojIENvcnJlbGF0aW9uIGJldHdlZW4gYWdlIGFuZCB0cmVzdGJzDQpjb3IobXlfZGF0YSRhZ2UsIG15X2RhdGEkY2hvbCkNCg0KYGBgDQpgYGB7cn0NCiMgQ29ycmVsYXRpb24gYmV0d2VlbiBjaG9sIGFuZCB0cmVzdGJzDQpjb3IobXlfZGF0YSRjaG9sLCBteV9kYXRhJHRyZXN0YnBzKQ0KDQpgYGANCmBgYHtyfQ0KIyBDb3JyZWxhdGlvbiBiZXR3ZWVuIHRoYWxjaCBhbmQgdHJlc3Ricw0KY29yKG15X2RhdGEkdGhhbGNoLCBteV9kYXRhJHRyZXN0YnBzKQ0KDQpgYGANCg0KYGBge3J9DQojIENvcnJlbGF0aW9uIGJldHdlZW4gYWdlIGFuZCB0aGFsY2gNCmNvcihteV9kYXRhJGFnZSwgbXlfZGF0YSR0aGFsY2gpDQoNCmBgYA0KYGBge3J9DQpgYGANCg0KDQpgYGB7cn0NCiMgQ29ycmVsYXRpb24gYmV0d2VlbiBhZ2UgYW5kIHNsb3BlDQpjb3IobXlfZGF0YSRhZ2UsIG15X2RhdGEkc2xvcGUpDQoNCmBgYA0KDQojIENvcnJlbGF0aW9uIG1hdHJpeA0KYGBge3J9DQoNCiMgU2VsZWN0IG9ubHkgbnVtZXJpYyBjb2x1bW5zDQpudW1lcmljX2NvbHMgPC0gc2FwcGx5KG15X2RhdGEsIGlzLm51bWVyaWMpDQpjb3JyZWxhdGlvbl9tYXRyaXggPC0gY29yKG15X2RhdGFbLCBudW1lcmljX2NvbHNdLCB1c2UgPSAiY29tcGxldGUub2JzIikNCg0KIyBQcmludCB0aGUgY29ycmVsYXRpb24gbWF0cml4DQpwcmludChjb3JyZWxhdGlvbl9tYXRyaXgpDQoNCg0KYGBgDQpgYGB7cn0NCmxpYnJhcnkoY29ycnBsb3QpDQoNCiMgQ2FsY3VsYXRlIGNvcnJlbGF0aW9uIG1hdHJpeA0KbnVtZXJpY19jb2xzIDwtIHNhcHBseShteV9kYXRhLCBpcy5udW1lcmljKQ0KY29ycmVsYXRpb25fbWF0cml4IDwtIGNvcihteV9kYXRhWywgbnVtZXJpY19jb2xzXSwgdXNlID0gImNvbXBsZXRlLm9icyIpDQoNCiMgQ3JlYXRlIGhlYXRtYXAgd2l0aCBjb3JyZWxhdGlvbiB2YWx1ZXMNCmNvcnJwbG90KGNvcnJlbGF0aW9uX21hdHJpeCwgDQogICAgICAgICBtZXRob2QgPSAiY29sb3IiLA0KICAgICAgICAgdHlwZSA9ICJ1cHBlciIsDQogICAgICAgICBvcmRlciA9ICJoY2x1c3QiLA0KICAgICAgICAgdGwuY29sID0gImJsYWNrIiwNCiAgICAgICAgIHRsLnNydCA9IDQ1LA0KICAgICAgICAgYWRkQ29lZi5jb2wgPSAiYmxhY2siLA0KICAgICAgICAgbnVtYmVyLmNleCA9IDAuNywNCiAgICAgICAgIGRpYWcgPSBGQUxTRSkNCmBgYA0KDQoNCmBgYHtyfQ0KbGlicmFyeShwbG90bHkpDQoNCnBsb3RfbHkoDQogIHggPSBjb2xuYW1lcyhjb3JyZWxhdGlvbl9tYXRyaXgpLA0KICB5ID0gY29sbmFtZXMoY29ycmVsYXRpb25fbWF0cml4KSwNCiAgeiA9IGNvcnJlbGF0aW9uX21hdHJpeCwNCiAgdHlwZSA9ICJoZWF0bWFwIiwNCiAgY29sb3JzY2FsZSA9ICJWaXJpZGlzIiwNCiAgaG92ZXJpbmZvID0gIngreSt6Ig0KKSAlPiUgDQogIGxheW91dCgNCiAgICB0aXRsZSA9ICJJbnRlcmFjdGl2ZSBDb3JyZWxhdGlvbiBNYXRyaXgiLA0KICAgIHhheGlzID0gbGlzdCh0aWNrYW5nbGUgPSA0NSksDQogICAgbWFyZ2luID0gbGlzdChsID0gMTAwLCByID0gMTAwLCBiID0gMTUwKQ0KICApDQpgYGANCg0KIyBCb3ggUGxvdCAoTnVtZXJpYyB2cy4gQ2F0ZWdvcmljYWwpDQpgYGB7cn0NCiMgQm94cGxvdDogQ2hvbGVzdGVyb2wgYnkgSGVhcnQgRGlzZWFzZSBTdGF0dXMNCmJveHBsb3QoY2hvbCB+IG51bSwgZGF0YSA9IG15X2RhdGEsDQogICAgICAgIG1haW4gPSAiQm94cGxvdCBvZiBDaG9sZXN0ZXJvbCBieSBIZWFydCBEaXNlYXNlIFN0YXR1cyIsDQogICAgICAgIHhsYWIgPSAiSGVhcnQgRGlzZWFzZSIsDQogICAgICAgIHlsYWIgPSAiQ2hvbGVzdGVyb2wiLA0KICAgICAgICBjb2wgPSBjKCJza3libHVlIiwgInRvbWF0byIpKQ0KDQpgYGANCiMgQm94IFBsb3QgKE51bWVyaWMgdnMuIENhdGVnb3JpY2FsKQ0KYGBge3J9DQojIEJveHBsb3Q6IFRoYWxjaCBieSBTZXgNCmJveHBsb3QodGhhbGNoIH4gc2V4LCBkYXRhID0gbXlfZGF0YSwNCiAgICAgICAgbWFpbiA9ICJCb3hwbG90IG9mIE1heCBIZWFydCBSYXRlIGJ5IFNleCIsDQogICAgICAgIHhsYWIgPSAiU2V4IiwNCiAgICAgICAgeWxhYiA9ICJNYXggSGVhcnQgUmF0ZSAodGhhbGNoKSIsDQogICAgICAgIGNvbCA9IGMoImxpZ2h0cGluayIsICJsaWdodGJsdWUiKSkNCg0KDQpgYGANCiMgQm94IFBsb3QgKE51bWVyaWMgdnMuIENhdGVnb3JpY2FsKQ0KYGBge3J9DQojIEJveHBsb3Q6IE9sZHBlYWsgYnkgQ2hlc3QgUGFpbiBUeXBlDQpib3hwbG90KG9sZHBlYWsgfiBjcCwgZGF0YSA9IG15X2RhdGEsDQogICAgICAgIG1haW4gPSAiQm94cGxvdCBvZiBTVCBEZXByZXNzaW9uIChPbGRwZWFrKSBieSBDaGVzdCBQYWluIFR5cGUiLA0KICAgICAgICB4bGFiID0gIkNoZXN0IFBhaW4gVHlwZSIsDQogICAgICAgIHlsYWIgPSAiT2xkcGVhayIsDQogICAgICAgIGNvbCA9IHJhaW5ib3cobGVuZ3RoKHVuaXF1ZShteV9kYXRhJGNwKSkpKQ0KDQpgYGANCiMgQm94IFBsb3QgKE51bWVyaWMgdnMuIENhdGVnb3JpY2FsKQ0KYGBge3J9DQojIEJveHBsb3Q6IFRyZXN0YnBzIGJ5IFNsb3BlDQpib3hwbG90KHRyZXN0YnBzIH4gc2xvcGUsIGRhdGEgPSBteV9kYXRhLA0KICAgICAgICBtYWluID0gIkJveHBsb3Qgb2YgUmVzdGluZyBCUCBieSBTbG9wZSBvZiBQZWFrIEV4ZXJjaXNlIFNUIiwNCiAgICAgICAgeGxhYiA9ICJTbG9wZSBUeXBlIiwNCiAgICAgICAgeWxhYiA9ICJSZXN0aW5nIEJsb29kIFByZXNzdXJlIiwNCiAgICAgICAgY29sID0gYygib3JhbmdlIiwgImN5YW4iLCAibGlnaHRncmVlbiIpKQ0KYGBgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpnZ3Bsb3QobXlfZGF0YSwgYWVzKHggPSBhcy5mYWN0b3IobnVtKSwgeSA9IGNob2wsIGZpbGwgPSBhcy5mYWN0b3IobnVtKSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBnZ3RpdGxlKCJDaG9sZXN0ZXJvbCBMZXZlbHMgYnkgSGVhcnQgRGlzZWFzZSBDbGFzcyIpICsNCiAgeGxhYigiSGVhcnQgRGlzZWFzZSBDbGFzcyAobnVtKSIpICsNCiAgeWxhYigiU2VydW0gQ2hvbGVzdGVyb2wgKG1nL2RsKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCg0KDQpgYGANCg0KDQoNCmluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KIyBQYWlyIFBsb3QgKE11bHRpdmFyaWF0ZSBWaXN1YWxpemF0aW9uKQ0KYGBge3J9DQoNCnBhaXJzKG15X2RhdGFbLCBjKCJhZ2UiLCAidHJlc3RicHMiLCAiY2hvbCIsICJ0aGFsY2giLCAib2xkcGVhayIpXSwNCiAgICAgIGNvbCA9IG15X2RhdGEkbnVtLA0KICAgICAgcGNoID0gMTksDQogICAgICBtYWluID0gIlBhaXIgUGxvdCBvZiBTZWxlY3RlZCBGZWF0dXJlcyBDb2xvcmVkIGJ5IEhlYXJ0IERpc2Vhc2UiKQ0KDQpgYGANCg0KDQojIFVzaW5nIGdncGxvdDIgZm9yIEVuaGFuY2VkIFZpc3VhbGl6YXRpb24NCmBgYHtyfQ0KDQpsaWJyYXJ5KGdncGxvdDIpDQoNCm15X2RhdGEkbnVtIDwtIGFzLmZhY3RvcihteV9kYXRhJG51bSkNCg0KDQpnZ3Bsb3QobXlfZGF0YSwgYWVzKHggPSBhZ2UsIHkgPSB0aGFsY2gsIGNvbG9yID0gbnVtKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArDQogIGdndGl0bGUoIkFnZSB2cyBNYXggSGVhcnQgUmF0ZSAodGhhbGNoKSBieSBIZWFydCBEaXNlYXNlIENsYXNzIikgKw0KICB4bGFiKCJBZ2UiKSArDQogIHlsYWIoIk1heCBIZWFydCBSYXRlIEFjaGlldmVkICh0aGFsY2gpIikNCg0KDQpgYGANCg0KDQojIFVzaW5nIGdncGxvdDIgZm9yIEVuaGFuY2VkIFZpc3VhbGl6YXRpb24NCmBgYHtyfQ0KDQoNCg0KZ2dwbG90KG15X2RhdGEsIGFlcyh4ID0gdHJlc3RicHMsIHkgPSBjaG9sLCBjb2xvciA9IG51bSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSkgKw0KICBnZ3RpdGxlKCJSZXN0aW5nIEJsb29kIFByZXNzdXJlIHZzIENob2xlc3Rlcm9sIGJ5IEhlYXJ0IERpc2Vhc2UgQ2xhc3MiKSArDQogIHhsYWIoIlJlc3RpbmcgQmxvb2QgUHJlc3N1cmUgKHRyZXN0YnBzKSIpICsNCiAgeWxhYigiU2VydW0gQ2hvbGVzdGVyb2wgKGNob2wpIikNCg0KDQpgYGANCg0KDQojIFVzaW5nIGdncGxvdDIgZm9yIEVuaGFuY2VkIFZpc3VhbGl6YXRpb24NCmBgYHtyfQ0KDQojIFNjYXR0ZXIgcGxvdCB3aXRoIHJlZ3Jlc3Npb24gbGluZSBmb3IgdGhhbGNoIHZzIGNob2wNCmdncGxvdChteV9kYXRhLCBhZXMoeCA9IHRoYWxjaCwgeSA9IGNob2wsIGNvbG9yID0gbnVtKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArDQogIGdndGl0bGUoIk1heGltdW0gSGVhcnQgUmF0ZSBBY2hpZXZlZCB2cyBDaG9sZXN0ZXJvbCBieSBIZWFydCBEaXNlYXNlIENsYXNzIikgKw0KICB4bGFiKCJNYXhpbXVtIEhlYXJ0IFJhdGUgQWNoaWV2ZWQgKHRoYWxjaCkiKSArDQogIHlsYWIoIlNlcnVtIENob2xlc3Rlcm9sIChjaG9sKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCg0KDQoNCmBgYA0KIyBVc2luZyBnZ3Bsb3QyIGZvciBFbmhhbmNlZCBWaXN1YWxpemF0aW9uDQpgYGB7cn0NCiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyaWVzDQpsaWJyYXJ5KGNhcmV0KQ0KDQojIFNlbGVjdCBudW1lcmljIGNvbHVtbnMgZm9yIHNjYWxpbmcNCm51bWVyaWNfY29scyA8LSBjKCJhZ2UiLCAidHJlc3RicHMiLCAiY2hvbCIsICJ0aGFsY2giLCAib2xkcGVhayIpDQoNCiMgQXBwbHkgc3RhbmRhcmRpemF0aW9uICh6LXNjb3JlIG5vcm1hbGl6YXRpb24pDQpwcmVQcm9jVmFsdWVzIDwtIHByZVByb2Nlc3MobXlfZGF0YVssIG51bWVyaWNfY29sc10sIG1ldGhvZCA9IGMoImNlbnRlciIsICJzY2FsZSIpKQ0KbXlfZGF0YV9zY2FsZWQgPC0gcHJlZGljdChwcmVQcm9jVmFsdWVzLCBteV9kYXRhWywgbnVtZXJpY19jb2xzXSkNCg0KIyBSZXBsYWNlIG9yaWdpbmFsIGNvbHVtbnMgd2l0aCBzY2FsZWQgdmVyc2lvbnMNCm15X2RhdGFbLCBudW1lcmljX2NvbHNdIDwtIG15X2RhdGFfc2NhbGVkDQoNCiMgVmVyaWZ5IHRoZSB0cmFuc2Zvcm1hdGlvbg0Kc3VtbWFyeShteV9kYXRhWywgbnVtZXJpY19jb2xzXSkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBCaW5hcnkgZW5jb2RpbmcgZm9yIHNleCAoTWFsZT0xLCBGZW1hbGU9MCkNCm15X2RhdGEkc2V4IDwtIGlmZWxzZShteV9kYXRhJHNleCA9PSAiTWFsZSIsIDEsIDApDQoNCiMgRW5zdXJlIGZicyBpcyBpbnRlZ2VyIChmYXN0aW5nIGJsb29kIHN1Z2FyID4gMTIwIG1nL2RsKQ0KbXlfZGF0YSRmYnMgPC0gYXMuaW50ZWdlcihteV9kYXRhJGZicykNCg0KIyBDb252ZXJ0IHRhcmdldCB2YXJpYWJsZSB0byBmYWN0b3INCm15X2RhdGEkbnVtIDwtIGFzLmZhY3RvcihteV9kYXRhJG51bSkNCg0KIyBJZGVudGlmeSBudW1lcmljIGNvbHVtbnMgZm9yIHNjYWxpbmcgKGV4Y2x1ZGluZyBhbHJlYWR5IGVuY29kZWQgY2F0ZWdvcmljYWxzIGFuZCB0YXJnZXQpDQpudW1lcmljX2NvbHMgPC0gYygiYWdlIiwgInRyZXN0YnBzIiwgImNob2wiLCAidGhhbGNoIiwgIm9sZHBlYWsiLCAiY2EiKQ0KDQojIEFwcGx5IHN0YW5kYXJkaXphdGlvbiAoei1zY29yZSBub3JtYWxpemF0aW9uKQ0KbGlicmFyeShjYXJldCkNCnByZVByb2NWYWx1ZXMgPC0gcHJlUHJvY2VzcyhteV9kYXRhWywgbnVtZXJpY19jb2xzXSwgbWV0aG9kID0gYygiY2VudGVyIiwgInNjYWxlIikpDQoNCiMgVmlldyB0aGUgc3RydWN0dXJlIG9mIHlvdXIgZGF0YQ0Kc3RyKG15X2RhdGEpDQpgYGANCg0KDQoNCg0KYGBge3J9DQojIENoZWNrIHRoZSBmaXJzdCBmZXcgcm93cw0KaGVhZChteV9kYXRhKQ0KYGBgDQoNCg0KYGBge3J9DQojIEZ1bmN0aW9uIHRvIGRldGVjdCBvdXRsaWVycyB1c2luZyBJUVIgbWV0aG9kDQpkZXRlY3Rfb3V0bGllcnMgPC0gZnVuY3Rpb24oeCkgew0KICBRMSA8LSBxdWFudGlsZSh4LCAwLjI1LCBuYS5ybSA9IFRSVUUpDQogIFEzIDwtIHF1YW50aWxlKHgsIDAuNzUsIG5hLnJtID0gVFJVRSkNCiAgSVFSIDwtIFEzIC0gUTENCiAgbG93ZXJfYm91bmQgPC0gUTEgLSAxLjUgKiBJUVINCiAgdXBwZXJfYm91bmQgPC0gUTMgKyAxLjUgKiBJUVINCiAgeCA8IGxvd2VyX2JvdW5kIHwgeCA+IHVwcGVyX2JvdW5kDQp9DQoNCiMgQ2hlY2sgZm9yIG91dGxpZXJzIGluIG51bWVyaWMgY29sdW1ucw0Kb3V0bGllcnNfbGlzdCA8LSBsYXBwbHkobXlfZGF0YVssIG51bWVyaWNfY29sc10sIGRldGVjdF9vdXRsaWVycykNCm91dGxpZXJzX2NvdW50IDwtIHNhcHBseShvdXRsaWVyc19saXN0LCBzdW0pDQpwcmludChvdXRsaWVyc19jb3VudCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBCb3hwbG90cyB0byB2aXN1YWxpemUgb3V0bGllcnMNCnBhcihtZnJvdyA9IGMoMiwgMykpDQpmb3IgKGNvbCBpbiBudW1lcmljX2NvbHMpIHsNCiAgYm94cGxvdChteV9kYXRhW1tjb2xdXSwgbWFpbiA9IHBhc3RlKCJCb3hwbG90IG9mIiwgY29sKSkNCn0NCnBhcihtZnJvdyA9IGMoMSwgMSkpDQpgYGANCg0KDQpgYGB7cn0NCiMgMS4gUm9idXN0IFNjYWxpbmcNCmxpYnJhcnkoY2FyZXQpDQpyb2J1c3Rfc2NhbGVyIDwtIHByZVByb2Nlc3MobXlfZGF0YVssIG51bWVyaWNfY29sc10sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9IGMoImNlbnRlciIsICJzY2FsZSIsICJZZW9Kb2huc29uIikpDQpteV9kYXRhWywgbnVtZXJpY19jb2xzXSA8LSBwcmVkaWN0KHJvYnVzdF9zY2FsZXIsIG15X2RhdGFbLCBudW1lcmljX2NvbHNdKQ0KDQojIDIuIENoZWNrIGZvciB6ZXJvLUlRUiBjb2x1bW5zDQp6ZXJvX2lxcl9jb2xzIDwtIHNhcHBseShteV9kYXRhWywgbnVtZXJpY19jb2xzXSwgZnVuY3Rpb24oeCkgSVFSKHgsIG5hLnJtID0gVFJVRSkgPT0gMCkNCmlmKGFueSh6ZXJvX2lxcl9jb2xzKSkgew0KICBtZXNzYWdlKCJDb2x1bW5zIHdpdGggSVFSPTA6ICIsIHBhc3RlKG5hbWVzKHplcm9faXFyX2NvbHMpW3plcm9faXFyX2NvbHNdLCBjb2xsYXBzZSA9ICIsICIpKQ0KDQogIHZhbGlkX2NvbHMgPC0gbmFtZXMoemVyb19pcXJfY29scylbIXplcm9faXFyX2NvbHNdDQp9DQoNCiMgMy4gTXVsdGl2YXJpYXRlIE91dGxpZXIgRGV0ZWN0aW9uICh3aXRoIGZhbGxiYWNrKQ0KbGlicmFyeShNQVNTKQ0KDQppZihsZW5ndGgodmFsaWRfY29scykgPiAxKSB7ICAjIE5lZWQgYXQgbGVhc3QgMiBjb2x1bW5zIGZvciBjb3ZhcmlhbmNlDQogIHRyeUNhdGNoKHsNCiAgICBtY2QgPC0gY292Lm1jZChteV9kYXRhWywgdmFsaWRfY29sc10pDQogICAgbWFoYWxhbm9iaXNfZGlzdCA8LSBtYWhhbGFub2JpcyhteV9kYXRhWywgdmFsaWRfY29sc10sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1jZCRjZW50ZXIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1jZCRjb3YpDQogICAgY3V0b2ZmIDwtIHFjaGlzcSgwLjk5LCBkZiA9IGxlbmd0aCh2YWxpZF9jb2xzKSkNCiAgICBvdXRsaWVycyA8LSBtYWhhbGFub2Jpc19kaXN0ID4gY3V0b2ZmDQogICAgDQogICAgIyA0LiBIYW5kbGUgb3V0bGllcnMgKGNhcHBpbmcgYXBwcm9hY2gpDQogICAgbXlfZGF0YVtvdXRsaWVycywgdmFsaWRfY29sc10gPC0gDQogICAgICBhcHBseShteV9kYXRhW291dGxpZXJzLCB2YWxpZF9jb2xzXSwgMiwgDQogICAgICAgICAgICBmdW5jdGlvbih4KSBwbWluKHBtYXgoeCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWFudGlsZSh4LCAwLjAxLCBuYS5ybSA9IFRSVUUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWFudGlsZSh4LCAwLjk5LCBuYS5ybSA9IFRSVUUpKSkNCiAgfSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7DQogICAgbWVzc2FnZSgiTUNEIGZhaWxlZCwgdXNpbmcgcmVndWxhciBNYWhhbGFub2JpczogIiwgZSRtZXNzYWdlKQ0KICAgICMgRmFsbGJhY2sgdG8gcmVndWxhciBNYWhhbGFub2Jpcw0KICAgIG1haGFsYW5vYmlzX2Rpc3QgPC0gbWFoYWxhbm9iaXMobXlfZGF0YVssIHZhbGlkX2NvbHNdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xNZWFucyhteV9kYXRhWywgdmFsaWRfY29sc10pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3YobXlfZGF0YVssIHZhbGlkX2NvbHNdKSkNCiAgICBjdXRvZmYgPC0gcWNoaXNxKDAuOTksIGRmID0gbGVuZ3RoKHZhbGlkX2NvbHMpKQ0KICAgIG91dGxpZXJzIDwtIG1haGFsYW5vYmlzX2Rpc3QgPiBjdXRvZmYNCiAgfSkNCn0gZWxzZSB7DQogIG1lc3NhZ2UoIkluc3VmZmljaWVudCB2YWxpZCBjb2x1bW5zIGZvciBtdWx0aXZhcmlhdGUgb3V0bGllciBkZXRlY3Rpb24iKQ0KICBvdXRsaWVycyA8LSByZXAoRkFMU0UsIG5yb3cobXlfZGF0YSkpDQp9DQoNCiMgNS4gRmluYWwgVmVyaWZpY2F0aW9uDQpzdW1tYXJ5KG15X2RhdGFbLCBudW1lcmljX2NvbHNdKQ0KYm94cGxvdChteV9kYXRhWywgbnVtZXJpY19jb2xzXSwgbWFpbiA9ICJQb3N0LVByb2Nlc3NpbmcgRGlzdHJpYnV0aW9ucyIpDQpgYGANCg0KDQoNCg==